Skip to content

fix(resolution): drop nested-local candidates from bare-name resolution (#1230)#1234

Open
umi008 wants to merge 1 commit into
colbymchenry:mainfrom
umi008:fix/1230-python-builtin-matching
Open

fix(resolution): drop nested-local candidates from bare-name resolution (#1230)#1234
umi008 wants to merge 1 commit into
colbymchenry:mainfrom
umi008:fix/1230-python-builtin-matching

Conversation

@umi008

@umi008 umi008 commented Jul 9, 2026

Copy link
Copy Markdown

Summary

Bare-name resolution in matchByExactName and matchFuzzy was promoting any same-named node in the caller's file as a candidate, including locals captured by a sibling function. A Python attribute call like ", ".join(sorted(unresolved)) was matching a same-named local join defined inside a different function of the same file and producing a wrong calls edge.

Root cause

Same-file proximity is the right tiebreaker in general, but it has no business crossing function boundaries. The qualifiedName-aware and import-aware strategies don't help here because the ref has no . or :: to key them on — it is a bare identifier like join — so the resolver fell all the way through to the proximity-only fallback.

Fix

In matchByExactName and matchFuzzy, drop candidates whose enclosing scope is a function/method the caller cannot reach lexically.

  • Cross-file candidates pass through unchanged (imports, not lexical scope, govern them).
  • Same-file, module-level candidates pass through unchanged.
  • Same-file, function-nested candidates are accepted only when the caller's container IS the candidate's container, or a strict ancestor of the caller — i.e., the caller can lexically see the candidate.

One-liner cases (C++ class Calculator { Calculator(int){} }; where the class and its constructor share one line) are disambiguated by the extractor's qualifiedName hierarchy: a constructor's Calculator::Calculator is recognized as a CHILD of the class Calculator, not the other way around, so the constructor isn't mistakenly treated as the class's container.

Validation

  • New __tests__/python-builtin-scope-filter.test.ts: 3/3 pass, covering the literal-receiver repro from the issue, the positive case where the enclosing function calls its own nested helper, and the OOP variant (sibling class method named the same).
  • __tests__/resolution.test.ts: 169/169 pass — including the one-liner C++ instantiates regression test (C++ stack-allocation syntax not recorded as 'instantiates' edge #1035) that exercises class/method disambiguation by qualifiedName. The instantiates edge for Calculator calc(0) correctly targets class:Calculator, not the same-named constructor method.
  • npm run build: clean (no TS warnings, no errors).

Closes #1230.

…on (colbymchenry#1230)

A Python attribute call like ', '.join(sorted(unresolved)) was matching
a same-named local 'join' defined inside a different function of the
same file (the user-facing example from the issue) and producing a
wrong 'calls' edge. The same trap applied to any language where the
same name lives lexically inside one function but is looked up from
another — a class method named 'X' on Helper resolving as a callee of
Worker's same-named method, or a JS/TS nested arrow function being
matched as a callee of an outer function that simply shares the name.

Root cause: bare-name resolution in matchByExactName and matchFuzzy
ignored lexical scope. Same-file proximity promoted any same-named
node in the file — including locals captured by a sibling function —
and the qualifiedName-aware 'imported' checks did not run because the
ref had no '.' or '::' to key them on.

Fix: in matchByExactName and matchFuzzy, drop candidates whose
enclosing scope is a function/method that the caller cannot reach
lexically. 'Unreachable' means: same file, candidate's container is
neither the caller's container nor a strict ancestor of the caller.
Cross-file candidates pass through unchanged (imports, not lexical
scope, govern their reachability). One-liner cases (C++ class
'class Calculator { Calculator(int){} };' where the class and its
constructor share one line) are disambiguated by the extractor's
qualifiedName hierarchy — a constructor's qn
'Calculator::Calculator' is recognized as a CHILD of the class
'Calculator', not the other way around.

Validation:
- new __tests__/python-builtin-scope-filter.test.ts: 3 tests, covering
  the literal-receiver repro from the issue, the positive case where
  the enclosing function calls its own nested helper, and the OOP
  variant (sibling class method).
- full __tests__/resolution.test.ts: 169/169 pass, including the
  one-liner C++ instantiates regression test (colbymchenry#1035) that exercises
  class/method disambiguation by qualifiedName.
@umi008 umi008 force-pushed the fix/1230-python-builtin-matching branch from fc3b9b5 to d3ee4ca Compare July 10, 2026 21:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[python] matching built-ins

1 participant